﻿if (typeof jQuery === 'undefined') {
    throw new Error('jquery.form JavaScript requires jQuery');
}

if (typeof Plugin === 'undefined') {
    throw new Error('jquery.form JavaScript requires Plugin');
}


(function ($) {
    'use strict';

    var Form = function (el, options) {
        this.$el = $(el);
        this.namespace = _config_.form.namespace;
        this.defaults = _config_.form.defaults;
        this.methods = _config_.form.methods;
        this.events = _config_.form.events;

        this.initials = $.com.getDataBindFromElement(this.$el);
        this.initials = $.extend(true, {}, _config_.form.initials, this.initials);

        //刷新或重新获取属于 form 的 field 和子 form 及父级 form
        this.getFormMember();
        this.originals = $.extend(true, {}, this.defaults, this.initials);
        this.options = $.extend(true, {}, this.originals, options);
        this.options.serial = $.com.getSerial(16);
        this.options.data = options.data || ((this.$el.is(_config_.selector.formArray)) ? [] : {});
        if (!this.$el.is(_config_.selector.formArray)) {
            this.options.data.serial = this.options.serial;
        }

        //处理id
        this.getNamedPrefix();
        this.$el.data(_config_.form.namespace, this);
        this.init();
    };

    Form.prototype = new Plugin();

    Form.prototype.init = function () {
        this.initData();
    };

    Form.prototype.reInit = function () {
        if ($.com.isJqueryObject(this.$fields)) {
            this.$fields.field('reInit');
            this.$popups.popup('init');
            $(_config_.selector.tooltip).tooltip();
        }

        if ($.com.isJqueryObject(this.$buttons)) {
            this.$buttons.button();
        }

        if ($.com.isJqueryObject(this.$forms)) {
            this.$forms.form('reInit');
        }
    };

    Form.prototype.initForm = function () {
        this.execute(this.options.loading);
        //draw Form
        if (!this.$el.is(_config_.selector.formArray)) {
            this.options.data.serial = this.options.serial;
        }

        this.$el.attr(_config_.attribute.serial, this.options.serial);
        if ($.com.hasValue(this.options.data)) {
            this.setRemovable();
        }

        if ($.com.isJqueryObject(this.$buttons)) {
            this.$buttons.button();
        }

        if ($.com.isJqueryObject(this.$fields)) {
            this.$fields.field();
            this.$popups.popup();
            this.$el.find(_config_.selector.tooltip).tooltip();
        }

        this.refresh();
        this.readonly();
        this.display();
        this.authorizeButton();
        this.$el.attr(_config_.attribute.initial, true);
        this.execute(this.options.loaded);
    };

    Form.prototype.initData = function () {
        //现在前置事件只需返回 false 就可以阻止事件继续执行
        var ret = this.execute(this.options.binding);
        if ($.com.hasValue(ret) && ret === false) {
            return;
        }

        if (this.options.side === 'server' && $.com.hasValue(this.options.url)) {
            $.api.ajax(this.options.url, '', { serial: $.com.getSerial() }, this.options.httpMethod, data => {
                this.options.editable = true;
                this.options.data = data;
                if ($.isFunction(this.options.dataAdapter)) {
                    this.options.data = this.options.dataAdapter(data) || {};

                    if (!$.com.isSimpleType(this.options.data) && !$.com.isArray(this.options.data) && this.options.objectToArray) {
                        this.options.data = $.com.objectToArray(this.options.data, this.options.dataValueField, this.options.dataTextField)
                    }

                    if (this.options.type !== _config_.default.arrayTypeName && $.com.isArray(this.options.data)) {
                        this.options.data = this.options.data[0] || {};
                    }
                }

                this.execute(this.options.bound);
                this.initForm();
            });
        } else {
            this.execute(this.options.bound);
            this.initForm();
        }
    };

    /**
     * 连接当前组件和父级组件的数据引用关系
     */
    Form.prototype.refreshFormDataReference = function () {
        if ($.com.isJqueryObject(this.$form)) {
            let form = this.$form.data(_config_.form.namespace);
            if ($.com.hasValue(form)) {
                //传递可编辑状态
                this.options.editable = form.options.editable;

                //修复引用
                if (form.options.type === _config_.default.arrayTypeName) {
                    //if (!$.com.isArray(form.options.data)) {
                    //    form.options.data = [];
                    //}

                    // form 数组不能嵌套 form 数组, 所以数组里只能是对象
                    if (!form.options.data.exist(x => x.serial === this.options.data.serial)) {
                        form.options.data.push(this.options.data);
                    }
                } else {
                    //修复数组
                    if (!(this.options.data instanceof Array) && this.options.data.hasOwnProperty('length')) {
                        this.options.data = $.com.toArray(this.options.data);
                    }

                    //排序
                    if ($.com.isArray(this.options.data)) {
                        this.options.data.sort((x, y) => {
                            let xcon = ($.com.hasValue(x.removable) || x[this.options.dataStateField] === _config_.default.dataState.deleted) ? 1 : 0;
                            let ycon = ($.com.hasValue(y.removable) || y[this.options.dataStateField] === _config_.default.dataState.deleted) ? 1 : 0;
                            if (xcon !== ycon) {
                                return xcon - ycon;
                            }

                            xcon = $.com.hasValue(x[this.options.dataOrderByField]) ? x[this.options.dataOrderByField] : 0;
                            ycon = $.com.hasValue(y[this.options.dataOrderByField]) ? y[this.options.dataOrderByField] : 0;
                            if (xcon < ycon) {
                                return -1;
                            }
                            if (xcon > ycon) {
                                return 1;
                            }

                            return 0;
                        });
                    }

                    form.options.data[this.options.field] = this.options.data;
                }
            }
        }
    };

    Form.prototype.getFormMember = function () {
        //子级field组件
        this.$fields = $(_config_.selector.default);
        //子级form组件
        this.$forms = $(_config_.selector.default);
        //子级form组件
        this.$buttons = $(_config_.selector.default);
        //父级form组件
        this.$form = this.$el.attr(_config_.attribute.formAttr);
        if ($.com.hasValue(this.$form)) {
            this.$form = $(this.$form).first();
        } else {
            this.$form = this.$el.parents(_config_.selector.form).first();
        }

        var fields = this.$el.find(_config_.selector.field).not(_config_.selector.form).not(_config_.selector.grid);
        if ($.com.isJqueryObject(fields)) {
            fields.each((index, element) => {
                if ($(element).isBelongToForm(this.$el)) {
                    this.$fields.push(element);
                }
            });
        }

        var buttons = this.$el.find(_config_.selector.button);
        if ($.com.isJqueryObject(buttons)) {
            buttons.each((index, element) => {
                if ($(element).isBelongToForm(this.$el)) {
                    this.$buttons.push(element);
                }
            });
        }

        var forms = this.$el.find(_config_.selector.form);
        if ($.com.isJqueryObject(forms)) {
            forms.each((index, element) => {
                if ($(element).isBelongToForm(this.$el)) {
                    this.$forms.push(element);
                }
            });
        }

        //this.$validator = undefined;
        //父级弹出框
        this.$popup = this.$el.closest(_config_.selector.popupWindow);
        //子级弹出框启动器
        this.$popups = this.$fields.filter(_config_.selector.popup);
        this.$tooltips = this.$fields.filter(_config_.selector.tooltip);

        //grid提供给form子组件使用, 这样子就不需要给每个需要使用grid的组件绑定grid
        if (this.$popup.hasValue()) {
            this.$grid = this.$popup.find(this.initials.grid);
        } else if (this.$form.hasValue()) {
            this.$grid = this.$form.find(this.initials.grid);
        } else {
            this.$grid = $(this.initials.grid || _config_.selector.default);
        }
        if (this.$grid.hasValue() && this.$grid.length > 1) {
            this.$grid = this.$grid.first();
        }

        if ($.com.hasValue(this.initials.template)) {
            this.$template = $(this.initials.template).children();
            this.$templateContainer = this.$el.find('.template-container');
        }
    };

    Form.prototype.readonly = function (arg) {
        this.options.readonly = $.com.hasValue(arg) ? arg : this.options.readonly;

        if ($.com.isJqueryObject(this.$fields)) {
            this.$fields.field('readonly', this.options.readonly);
        }

        if ($.com.isJqueryObject(this.$forms)) {
            this.$forms.form('readonly', this.options.readonly);
        }
    };

    Form.prototype.display = function (arg) {
        this.options.display = $.com.hasValue(arg) ? arg : this.options.display;
        if ($.com.hasValue(this.options.display) && !!this.options.display) {
            this.$el.show();
        } else {
            this.$el.hide();
        }
    };

    /**
     * 重新设置组件状态
     * @param {any} arg
     */
    Form.prototype.setStatus = function (arg) {
        if ($.com.isJqueryObject(this.$fields)) {
            this.$fields.field(arg);
        }

        if ($.com.isJqueryObject(this.$forms)) {
            this.$forms.form(arg);
        }
    };

    /**
     * 使用默认值设置组件及子组件的值, 回写 form
     * @param {any} arg
     */
    Form.prototype.clear = function () {
        this.setStatus('clear');
        this.writeBack();
    };

    /**
     * 使用初始值设置组件及子组件的值, 回写 form
     * @param {any} arg
     */
    Form.prototype.reset = function () {
        this.setStatus('reset');
        this.writeBack();
    };

    /**
     * 使用给定值设置组件及子组件的值, 不回写 form
     * @param {any} arg
     */
    Form.prototype.refresh = function (data, force) {
        //现在前置事件只需返回 false 就可以阻止事件继续执行
        var ret = this.execute(this.options.changing, data);
        if ($.com.hasValue(ret) && ret === false) {
            return;
        }

        if (!$.com.isNullOrEmpty(data) || $.com.hasValue(force) && force) {
            this.options.data = data;
        }

        if (this.options.type !== _config_.default.arrayTypeName && $.com.hasValue(this.options.data)) {
            this.options.data.serial = this.options.serial;
        }

        this.refreshFormDataReference();

        //刷新 from 中的 fields
        if ($.com.isJqueryObject(this.$fields)) {
            this.$fields.field('refresh', undefined, force);

            //初始化附件
            this.options.withAttachments = this.options.withAttachments || (this.$fields.is(_config_.selector.withAttachments) ? _config_.default.withAttachments : false);
            if (!!this.options.withAttachments) {
                this.initAttachments();
            }
        }

        //刷新 form 中的 buttons
        if ($.com.isJqueryObject(this.$buttons)) {
            this.$buttons.button('display');
        }

        //初始化form, 分四种情况, 
        //1. 新增模式, 无数据, 有模板且为 array 且 autoDraw=true, 则画一个空的, 否则直接初始化
        //2. 新增模式, 无数据, 无模板且为 array 且 autoDraw=false, 此时 form 中应该已经存在子元素， 应该直接初始化
        //3. 编辑模式或详情模式, 有数据, 有模版且为 array 则按数据画出来子 form;  有数据无模板且为 array, 而且子 form 存在, 则使用数据和子 form 初始化
        //4. 编辑模式或详情模式, 有数据, 无模板且数据不是 array, 则用数据初始化子 form
        if (this.options.type === _config_.default.arrayTypeName) {
            //当前 form 为数组, 子级 form 需根据新增编辑区分
            if (this.options.editable || this.options.detailable) {
                //编辑模式或详情模式， 如有 removable==true 的数据，需预先排序让这种数据排到最后边
                if ($.com.isJqueryObject(this.$template)) {
                    //有模板，按数据画子 form
                    this.$forms = $(_config_.selector.default);
                    this.$templateContainer.empty();
                }

                if ($.com.isArray(this.options.data)) {
                    if ($.com.isJqueryObject(this.$template)) {
                        //有模板，按数据画子 form
                        for (var i = 0; i < this.options.data.length; i++) {
                            this.addForm(this.options.data[i]);
                        }
                    } else {
                        //无模板，按已存在的子 form 按数据画出界面
                        this.$forms.each((index, element) => {
                            let $form = $(element);
                            let data = this.options.data[index];
                            if ($.com.hasValue($form.data(_config_.form.namespace))) {
                                $form.form('refresh', data, force);
                            } else {
                                $form.form({ data: data });
                            }
                        });
                    }
                } else if ($.com.isJqueryObject(this.$template) && this.options.autoDraw) {
                    //编辑模式没有数据但有模板，且为 autoDraw，则画一个子 form
                    this.$forms = $(_config_.selector.default);
                    this.$templateContainer.empty();
                    this.addForm();
                }
            } else {
                //新增模式
                if ($.com.isJqueryObject(this.$template) && this.options.autoDraw) {
                    //有模板，且为 autoDraw，则画一个子 form
                    this.$forms = $(_config_.selector.default);
                    this.$templateContainer.empty();
                    this.addForm();
                } else {
                    //无模板此时不判断 autoDraw，此时认为子 form 已存在，则取子 form 初始化
                    this.$forms.each((index, element) => {
                        let $form = $(element);
                        let data = ($form.is(_config_.selector.formArray)) ? [] : {};
                        this.options.data.push(data);
                        if ($.com.hasValue($form.data(_config_.form.namespace))) {
                            $form.form('refresh', data, force);
                        } else {
                            $form.form({ data: data });
                        }
                    });
                }
            }
        } else {
            //当前 form 是单个对象, 子级 form 取值取 data[field]
            this.$forms.each((index, element) => {
                let $form = $(element);
                let field = $form.attr(_config_.attribute.field);
                let form = this.options.data[field];

                if (!$.com.hasValue(form)) {
                    if ($form.is(_config_.selector.formArray)) {
                        this.options.data[field] = [];
                    } else {
                        this.options.data[field] = {};
                    }
                }

                form = this.options.data[field];
                if (!form.removable) {
                    if ($.com.hasValue($form.data(_config_.form.namespace))) {
                        $form.form('refresh', form, force);
                    } else {
                        $form.form({ data: form });
                    }
                }
            });
        }

        this.execute(this.options.changed);
    };

    Form.prototype.setRemovable = function () {
        if (!($.com.hasValue(this.options.removable) && this.$el.is('[data-removable]'))) {
            if ($.com.isJqueryObject(this.$form) && this.$form.is('[data-removable]')) {
                this.options.removable = this.$form.data(_config_.form.namespace).options.removable;
            }

            let dataState = this.options.data[this.options.dataStateField];
            if ($.com.hasValue(this.options.data.removable) && !this.options.data.removable || $.com.hasValue(dataState) && dataState === _config_.default.dataState.deleted) {
                this.options.removable = false;
            }

            this.$el.attr('data-removable', this.options.removable);
        }
    };

    //使用模板为当前 form 添加一个子 form, 并自动刷新相关数据
    Form.prototype.addForm = function (data) {
        if ($.com.isJqueryObject(this.$template)) {
            var $template = this.$template.clone();
            if ($.isFunction(this.$el[this.options.drawPlacement])) {
                if ($.com.hasValue(data)) {
                    this.setRemovable();
                }

                var $availableForm = this.$forms.not('[data-removable]');
                var $removableForm = this.$forms.filter('[data-removable]');
                if ($.com.isJqueryObject($removableForm) && $.com.hasValue(this.options.removable)) {
                    if (this.options.drawPlacement === 'append') {
                        $removableForm.last().after($template);
                    } else {
                        $removableForm.first().before($template);
                    }
                } else if ($.com.isJqueryObject($availableForm)) {
                    if (this.options.drawPlacement === 'append') {
                        $availableForm.last().after($template);
                    } else {
                        $availableForm.first().before($template);
                    }
                } else {
                    this.$templateContainer[this.options.drawPlacement]($template);
                }

                var $form = $template;
                if (!$form.is(_config_.selector.form)) {
                    $form = $template.find(_config_.selector.form).first();
                }

                if ($form.hasValue()) {
                    if ($.com.isNullOrEmpty(data)) {
                        data = ($form.is(_config_.selector.formArray)) ? [] : {};
                        this.options.data.push(data);
                    } else if ($.com.isArray(this.options.data) && !this.options.data.exist(x => x.serial === data.serial)) {
                        //数据有值且当前 form 数据是数组, 则从 serial 字段查找判断是否重复, 不存在重复项则说数据来自外部传入, 则添加到当前集合
                        this.options.data.push(data);
                    }

                    //初始化子 form
                    $form.form({ data: data, removable: this.options.removable });
                    if (this.options.drawPlacement === 'append') {
                        this.$forms.push($form[0]);
                    } else {
                        this.$forms.splice(0, 0, $form[0]);
                    }

                    if (this.options.autoRefreshLayout) {
                        var form = $form.data(_config_.form.namespace);
                        if (($.com.hasValue(form.options.data.removable) || form.options.data[this.options.dataStateField] === _config_.default.dataState.deleted) && !form.$el.hasClass('removable')) {
                            form.$el.addClass('removable');
                        }
                    }
                }
            }
        }
    };

    //移除当前 form, 并自动刷新相关数据(刷新父级 form)
    Form.prototype.removeForm = function () {
        if ($.com.isJqueryObject(this.$form)) {
            var form = this.$form.data(_config_.form.namespace);
            if ($.com.hasValue(this.options.field) && $.com.hasValue(form) && $.com.hasValue(form.options) && $.com.hasValue(form.options.data)) {
                this.options.data.removable = this.options.removable;
                if (form.options.type === _config_.default.arrayTypeName && $.com.isArray(form.options.data)) {
                    form.options.data.remove(this.options.data, 'serial');
                } else {
                    delete form.options.data[this.options.field];
                }

                //保留当前 form 数据引用
                let data = this.options.data;
                //销毁当前 form
                this.destroy();

                if ($.com.hasValue(data.removable) && !data.removable && data[this.options.dataStateField] !== _config_.default.dataState.deleted) {
                    //处理逻辑删除, 不删除元素也不删除数据， 删除后的元素显示到底部
                    data[this.options.dataStateField] = _config_.default.dataState.deleted;
                    if (form.options.type === _config_.default.arrayTypeName && $.com.isArray(form.options.data)) {
                        form.options.data.push(data);
                    } else {
                        form.options.data[this.options.field] = data;
                    }

                    form.addForm(data);
                }

                if (form.options.autoRefreshLayout && (($.com.hasValue(data.removable) || data[this.options.dataStateField] === _config_.default.dataState.deleted) && !this.$el.hasClass('removable'))) {
                    this.$el.addClass('removable');
                }
            }
        } else {
            this.destroy();
        }
    };

    Form.prototype.writeBack = function () {
        this.$fields.field('writeBack');
        if (this.$forms.hasValue()) {
            this.$forms.form('writeBack');
        }
    };

    /**
     * 非引用形式提供数据
     * @returns
     */
    Form.prototype.getFormData = function () {
        this.writeBack();

        //TODO TBC 此处是否需要过滤非提交字段?
        //var data = $.extend(true, $.isArray(this.options.data) ? [] : {}, this.options.data);
        //let ignoreFields = this.options.ignoreFields;
        //ignoreFields = $.com.hasValue(ignoreFields) ? (ignoreFields + ',' + _config_.default.ignoreFields) : _config_.default.ignoreFields;
        //return $.com.cleanData(this.options.data, ignoreFields);
        return $.com.cleanData(this.options.data);
    };

    Form.prototype.getQueryExpression = function () {
        if ($.com.isJqueryObject(this.$fields)) {
            this.options.dataSource = {};
            var filters = [];
            var queryString = {};
            let dataSourceType = _config_.dataSource.grid.type;
            if ($.com.isJqueryObject(this.$grid)) {
                dataSourceType = this.$grid.data(_config_.grid.namespace).options.dataSource.type || dataSourceType;
            }

            $.each(this.$fields, function () {
                var field = $(this).field('getData');
                if ($.com.hasValue(field)) {
                    var filter = field.getQueryExpression(dataSourceType);
                    if (!$.com.isNullOrEmpty(filter)) {
                        let dst = field.options.dataSourceType || dataSourceType;
                        if (dst.startsWith('odata') && $.com.hasValue(field.options.logic) && $.com.hasValue(field.options.operator) && field.options.logic && field.options.operator) {
                            filters = filters.concat(filter);
                        } else {
                            queryString = $.extend(queryString, filter);
                        }
                    }
                }
            });

            if ($.com.isArray(filters)) {
                this.options.dataSource.filter = {
                    logic: this.options.logic,
                    filters: filters
                };
            }
            if (!$.com.isNullOrEmpty(queryString)) {
                this.options.dataSource.queryString = queryString;
                this.options.dataSource.transport = {
                    read: { data: queryString }
                };
            }
        }
    };

    Form.prototype.fillFormData = function (data) {
        this.refresh(data);
        this.writeBack();
        this.onChange();
    };

    Form.prototype.onChange = function () {
        if ($.com.isJqueryObject(this.$fields)) {
            this.$fields.change();
        }

        if ($.com.isJqueryObject(this.$forms)) {
            this.$forms.form('onChange');
        }
    }

    Form.prototype.initAttachments = function () {
        if (this.options.withAttachments && (this.options.editable || this.options.detailable) && this.options.data.id) {
            let url = `${_config_.default.attachmentGet}?businessKey=${this.options.data.id}`;
            $.api.get(url, data => {
                this.options.data[this.options.withAttachments] = data || [];
                this.$fields.filter(_config_.selector.withAttachments).field('refresh');
            });
        }
    };

    Form.prototype.destroy = function () {
        var formData = this.$form.data(_config_.form.namespace);
        if ($.com.hasValue(formData) && formData.$forms.hasValue()) {
            formData.$forms.removeItem(this.$el);
        }

        while ($.com.isJqueryObject(this.$fields)) {
            this.$fields.eq(0).field('destroy');
        }

        while ($.com.isJqueryObject(this.$forms)) {
            this.$forms.eq(0).form('destroy');
        }

        while ($.com.isJqueryObject(this.$buttons)) {
            this.$buttons.eq(0).button('destroy');
        }

        this.$el.removeData(_config_.form.namespace);
        this.$el.remove();
    };

    Form.prototype.validate = function (options) {
        var $invalid;
        var isValid = options
        this.options.valid = true;
        if (options !== '*' && options !== false) {
            options = (this.options.validator || []).concat(options || []);
        }

        options = options || [];

        if ($.isArray(options) || options === '*') {
            var that = this;
            if (this.options.type === _config_.default.arrayTypeName) {
                if (this.$forms.hasValue()) {
                    for (var i = this.$forms.length - 1; i >= 0; i--) {
                        var $this = this.$forms.eq(i);
                        var valid = true;
                        var validator = options;
                        valid = $this.form('validate', validator);
                        this.options.valid = this.options.valid && valid;
                    }
                }
            } else {
                for (var i = this.$forms.length - 1; i >= 0; i--) {
                    var $this = this.$forms.eq(i);
                    var valid = true;
                    var validator = options;

                    if (options !== '*') {
                        var field = $this.attr(_config_.attribute.field);
                        validator = options.first(function (x) {
                            return x.field === field;
                        });
                        if ($.com.hasValue(validator)) {
                            validator = validator['validator'];
                        } else {
                            validator = false;
                        }
                    }

                    valid = $this.form('validate', validator);
                    this.options.valid = this.options.valid && valid;
                }

                this.$fields.each(function () {
                    var $this = $(this);
                    var valid = true;
                    //此处只能给undefined, 因为要和field自己的条件合并, 传递false则会取消验证
                    var validator = undefined;

                    if (options !== '*') {
                        var field = $this.attr(_config_.attribute.field);
                        validator = options.first(function (x) {
                            return x === field || x.field === field;
                        });
                        if ($.com.hasValue(validator) && !that.options.validateWhenDisplay) {
                            validator = validator['validator'];
                        } else {
                            validator = false;
                        }
                    }

                    valid = $this.field('validate', validator);
                    that.options.valid = that.options.valid && valid;
                    if (!$.com.isJqueryObject($invalid) && !valid) {
                        $invalid = $this;
                    }
                });
            }
        }

        if ($.isFunction(this.options.validate) && isValid !== false) {
            this.options.valid = this.options.valid && this.execute(this.options.validate);
        }

        if ($.com.isJqueryObject($invalid) && !this.options.valid) {
            $.com.switchTabs($invalid);
            $invalid.focus();
        }

        return this.options.valid;
    };

    // plug-in
    $.fn.form = function (options) {
        var args = Array.prototype.slice.call(arguments, 1);
        if (args.length === 0) {
            args = undefined;
        }
        var value = undefined;
        this.each(function () {
            var $this = $(this);
            var data = $this.data(_config_.form.namespace);
            // initial if nover
            if ($.com.isNullOrEmpty(data)) {
                data = new Form(this, typeof options === 'object' && options);
                $this.data(_config_.form.namespace, data);
            }

            if (typeof options === 'string') {
                //if options is not a method
                if ($.inArray(options, data.methods) < 0) {
                    throw 'Unknown method: ' + options;
                }

                //if options is a plugin method, to execute
                value = data[options].apply(data, args);
            }
        });

        return typeof value === 'undefined' ? this : value;
    };
})(jQuery);